/*
 * $Id: SysCodeUpload.java 17 2014-08-28 10:52:50Z ratking $
 *
 * Copyleft (C) 2014 RatKing. All wrongs reserved.
 */
package cn.ratking.demo.jaws6.system.view;

import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.enterprise.context.Conversation;
import javax.enterprise.context.ConversationScoped;
import javax.enterprise.event.Event;
import javax.inject.Inject;
import javax.inject.Named;

import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.omnifaces.util.Messages;
import org.primefaces.event.FileUploadEvent;
import org.primefaces.model.UploadedFile;
import org.slf4j.Logger;

import cn.ratking.demo.jaws6.model.LogEvent;
import cn.ratking.demo.jaws6.model.SysCode;
import cn.ratking.demo.jaws6.system.service.SysCodeService;

/**
 * 批量导入系统代码.
 * 上传系统代码Excel文件，并导入数据库中；
 * 合作保险公司、代码类型、代码名称、代码值，这几列必须非空；
 * 如果多次导入相同的Excel文件，不应有副作用。
 */
@Named
@ConversationScoped
public class SysCodeUpload implements Serializable {

    /** 系统日志 */
    @Inject
    private transient Logger log;
    @Inject
    private SysCodeService sysCodeService;
    @Inject
    private Conversation conversation;
    /** 事件源 */
    @Inject
    private Event<LogEvent> logEventSrc;
    /** 上传文件 */
    private UploadedFile uf;
    /** 上传文件信息 */
    private String uploadMsg = null;
    /** 首行映射：key-Excel的首行中文标题 value-SysCode的英文属性名 */
    private final Map<String, String> columnsMap = new HashMap<String, String>();

    /** 初始化 */
    @PostConstruct
    public void init() {
        log.debug("Debug: SysCodeUpload.init(), @PostConstruct, this=" + this
                + ", conversation.isTransient()=" + conversation.isTransient());
        // 创建Excel标题与系统代码属性的对应关系（支持同义词）
        //columnsMap.put("序号", null);
        columnsMap.put("类型", "codeType");
        columnsMap.put("类别", "codeType");
        columnsMap.put("代码类型", "codeType");
        columnsMap.put("代码类别", "codeType");
        columnsMap.put("名", "codeName");
        columnsMap.put("名称", "codeName");
        columnsMap.put("代码名", "codeName");
        columnsMap.put("代码名称", "codeName");
        columnsMap.put("值", "codeValue");
        columnsMap.put("代码", "codeValue");
        columnsMap.put("代码值", "codeValue");
        columnsMap.put("是否启用", "enabled");
        columnsMap.put("是否有效", "enabled");
        columnsMap.put("排序", "orderNo");
        columnsMap.put("排序号", "orderNo");
        columnsMap.put("顺序号", "orderNo");
        columnsMap.put("排序序号", "orderNo");
    }

    /** 销毁 */
    @PreDestroy
    public void destroy() {
        log.debug("Debug: SysCodeUpload.destroy(), @PreDestroy, this=" + this
                + ", conversation.isTransient()=" + conversation.isTransient());
    }

    /**
     * 开始业务会话
     */
    public void beginConversation() {
        if (conversation.isTransient()) {
            conversation.begin();
        }
        conversation.setTimeout(300000); //5 * 60 * 1000 (5 minutes)
    }

    /**
     * 结束业务会话
     */
    public void endConversation() {
        if (!conversation.isTransient()) {
            conversation.end();
        }
    }

    public UploadedFile getUf() {
        return uf;
    }

    public void setUf(UploadedFile uf) {
        this.uf = uf;
    }

    public String getUploadMsg() {
        return uploadMsg;
    }

    public void setUploadMsg(String uploadMsg) {
        this.uploadMsg = uploadMsg;
    }

    /**
     * 文件上传事件处理
     *
     * @param event 文件上传事件
     */
    public void handleFileUpload(FileUploadEvent event) {
        this.uf = event.getFile();
        String contentType = uf.getContentType();
        String fileName = uf.getFileName();
        long size = uf.getSize();

        uploadMsg = "文件已经上传至服务器[" + fileName + "]";
        String msg = uploadMsg + "(contentType=" + contentType + ", size=" + size + ")";
        log.debug(msg);
        Messages.addGlobalInfo(msg);
    }

    /**
     * 添加/修改系统代码
     *
     * @return 导航页面
     */
    public String save() {
        if (uf == null) {
            String errorMsg = "尚未上传系统代码";
            log.error(errorMsg);
            Messages.addGlobalError(errorMsg);
            return null;
        }

        InputStream in = null;
        try {
            in = uf.getInputstream();
            // 抽取系统代码
            List<SysCode> sysCodeList = extractData(in);
            // 系统代码导入表T_SysCode中，同时查重
            // 返回插入记录数、更新记录数、重复记录数、错误记录数
            int[] count = sysCodeService.importSysCode(sysCodeList);
            // 结束业务会话
            endConversation();

            String msg = "上传并导入系统代码成功。总数=" + sysCodeList.size()
                    + ", 插入=" + count[0] + "，更新=" + count[1]
                    + "，重复=" + count[2] + "，错误=" + count[3];
            log.debug(msg);
            logEventSrc.fire(new LogEvent(LogEvent.LOGTYPE_APPLICATION, LogEvent.LOGLEVEL_INFO, msg));
            Messages.addGlobalInfo(msg);
        } catch (Exception ex) {
            String errorMsg = "上传并导入系统代码失败：" + ex.getMessage();
            log.error(errorMsg, ex);
            logEventSrc.fire(new LogEvent(LogEvent.LOGTYPE_APPLICATION, LogEvent.LOGLEVEL_ERROR, errorMsg));
            Messages.addGlobalError(errorMsg);
            return null;
        } finally {
            if (in != null) {
                try {
                    in.close();
                } catch (IOException ignore) {
                    // do nothing here.
                }
            }
        } // try-catch-finally
        return "sysCodeQuery?faces-redirect=true";
    }

    /**
     * 取消添加或修改操作，返回查询列表
     *
     * @return 导航页面
     */
    public String cancel() {
        // 结束业务会话
        endConversation();
        return "sysCodeQuery?faces-redirect=true";
    }

    /**
     * 抽取系统代码.
     * 对于POI来说，主要使用以下几个开发包：
     * HSSF：MS－Excel 97-2003(.xls)，基于 BIFF8 格式的 JAVA 接口。
     * XSSF：MS－Excel 2007+(.xlsx)，基于 OOXML 格式的 JAVA 接口。
     * HWPF：MS－Word 97-2003(.doc)，基于 BIFF8 格式的 JAVA 接口。只支持 .doc 文件简单的操作，读写能力有限。
     * XWPF：MS－Word 2007+(.docx)，基于 OOXML 格式的 JAVA 接口，较 HWPF 功能完善。
     * HSLF：MS－PowerPoint97-2003(.ppt)，支持 Microsoft PowerPoint 97（-2003）的简单操作。
     *
     * @param in 上传文件的输入流
     * @return 系统代码列表
     * @throws java.io.IOException IO异常
     */
    public List<SysCode> extractData(InputStream in) throws IOException {
        List<SysCode> sysCodeList = new ArrayList<SysCode>();
        Workbook wb = new HSSFWorkbook(in, false); // MS－Excel 97-2003(.xls)
        //Workbook wb = new XSSFWorkbook(in); // MS－Excel 2007+(.xlsx)

        int numberOfSheets = wb.getNumberOfSheets();
        for (int s = 0; s < numberOfSheets; s++) {
            Sheet sheet = wb.getSheetAt(s);
            //System.out.println("Debug: sheet[" + s + "]=" + sheet.getSheetName());
            int firstRowNum = sheet.getFirstRowNum();
            int lastRowNum = sheet.getLastRowNum();
            // int physicalNumberOfRows = sheet.getPhysicalNumberOfRows();
            //System.out.println("Debug: firstRowNum=" + firstRowNum + ", lastRowNum=" + lastRowNum + ", physicalNumberOfRows=" + physicalNumberOfRows);
            Map<Integer, String> columnMap = null;
            for (int r = firstRowNum; r <= lastRowNum; r++) {
                Row row = sheet.getRow(r);
                if (row == null) {
                    String warnMsg = "警告：第" + r + "行是null";
                    log.warn(warnMsg);
                    Messages.addGlobalWarn(warnMsg);
                    continue;
                }
                // 逐行抽取Excel数据
                String[] rowData = extractRow(row);
                if (r == firstRowNum) {
                    // 抽取首行的标题行
                    columnMap = extractFirstRow(rowData);
                } else {
                    // 非首行，逐行解析后入库
                    try {
                        SysCode sysCode = populateRpt(r, rowData, columnMap);
                        if (sysCode != null) {
                            sysCodeList.add(sysCode);
                        }
                    } catch (Exception ex) {
                        String errorMsg = "处理第" + r + "行记录时出错：" + ex.getMessage();
                        log.error(errorMsg, ex);
                        Messages.addGlobalError(errorMsg);
                        // 仅输出出错信息，不抛异常，继续处理下一行记录
                    }
                }
            } // for(r)
        } // for(s)
        return sysCodeList;
    }

    /**
     * 逐行抽取Excel数据
     *
     * @param row Excel的一行
     * @return 一行数据的字符串数组
     */
    private String[] extractRow(Row row) {
        int firstCellNum = row.getFirstCellNum();
        int lastCellNum = row.getLastCellNum();
        String[] result = new String[lastCellNum - firstCellNum];
        for (int c = firstCellNum; c < lastCellNum; c++) {
            Cell cell = row.getCell(c);
            if (cell != null) {
                result[c] = cell.toString();
                if (result[c] != null) {
                    // 剔除字符串前后空格
                    result[c] = result[c].trim();
                }
            } else {
                result[c] = null;
            }
        }
        return result;
    }

    /**
     * 抽取首行的标题行
     *
     * @param rowData
     * @return
     */
    private Map<Integer, String> extractFirstRow(String[] rowData) {
        log.debug("Debug: 首行标题行共 " + rowData.length + " 列");
        Map<Integer, String> columnMap = new HashMap<Integer, String>(rowData.length);
        for (int i = 0; i < rowData.length; i++) {
            String propertyName = columnsMap.get(rowData[i]);
            if (propertyName == null) {
                String warnMsg = "第[" + i + "]列标题“" + rowData[i] + "”找不到对应的属性名";
                log.warn(warnMsg);
                Messages.addGlobalWarn(warnMsg);
            } else {
                //log.debug("Debug: [" + i + "]发现标题：" + rowData[i] + " ==> " + propertyName);
                columnMap.put(i, propertyName);
            }
        }
        return columnMap;
    }

    /**
     * 组装系统代码.
     * 合作保险公司、代码类型、代码名称、代码值，这几列必须非空；
     *
     * @param rowData 从Excel中抽取的一行数据
     * @return 系统代码(如果内容非法，则返回null)
     */
    private SysCode populateRpt(int r, String[] rowData, Map<Integer, String> columnMap) {
        SysCode sysCode = new SysCode();
        for (int i = 0; i < rowData.length; i++) {
            String propertyValue = rowData[i];
            if (propertyValue == null || propertyValue.trim().length() == 0) {
                continue;
            }
            String propertyName = columnMap.get(i);
            if (propertyName == null) {
                continue;
            }
            if (propertyName.equals("codeType")) {
                // 代码类型
                sysCode.setCodeType(propertyValue);
            } else if (propertyName.equals("codeName")) {
                // 代码名称
                sysCode.setCodeName(propertyValue);
            } else if (propertyName.equals("codeValue")) {
                // 代码值
                sysCode.setCodeValue(propertyValue);
            } else if (propertyName.equals("enabled")) {
                // 是否启用(N-禁用 Y-启用)
                boolean enabled = (propertyValue.equalsIgnoreCase("Y")
                        || propertyValue.equals("1")
                        || propertyValue.equals("是")
                        || propertyValue.equals("启用")
                        || propertyValue.equals("有效"));
                sysCode.setEnabled(enabled);
            } else if (propertyName.equals("orderNo")) {
                // 排序序号
                Integer orderNo = Integer.valueOf(propertyValue.contains(".") ? propertyValue.substring(0, propertyValue.indexOf('.')) : propertyValue);
                sysCode.setOrderNo(orderNo);
            } else {
                log.error("舍弃无法处理的属性[" + propertyName + "=" + propertyValue + "]");
            }
        }
        // 合作保险公司、代码类型、代码名称、代码值，这几列必须非空
        if (sysCode.getCodeType() == null || sysCode.getCodeName() == null || sysCode.getCodeValue() == null) {
            String warnMsg = "第" + r + "行有空值：代码类型[" + sysCode.getCodeType()
                    + "]，代码名称[" + sysCode.getCodeName()
                    + "]，代码值[" + sysCode.getCodeValue() + "]";
            log.warn(warnMsg);
            Messages.addGlobalWarn(warnMsg);
            return null;
        }
        return sysCode;
    }
}
